package com.iacrqq.sedaf.application.config;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import com.iacrqq.sedaf.application.Application;
import com.iacrqq.sedaf.application.DefaultApplication;
import com.iacrqq.sedaf.application.adaptive.ApplicationAdjuster;
import com.iacrqq.sedaf.config.Configuration;
import com.iacrqq.sedaf.config.MemoryConfiguration;
import com.iacrqq.sedaf.event.DefaultEventDispatcher;
import com.iacrqq.sedaf.event.Event;
import com.iacrqq.sedaf.event.EventDispatcher;
import com.iacrqq.sedaf.event.EventHandler;
import com.iacrqq.sedaf.queue.Input;
import com.iacrqq.sedaf.queue.Output;
import com.iacrqq.sedaf.queue.Queue;
import com.iacrqq.sedaf.queue.adaptive.QueueAdjuster;
import com.iacrqq.sedaf.stage.DefaultStage;
import com.iacrqq.sedaf.stage.Stage;
import com.iacrqq.sedaf.stage.adaptive.StageAdjuster;

/**
 * 
 * @author sihai
 *
 */
public class ApplicationDefinition {
	
	private static final String QUEUE_NAME = "name";
	private static final String QUEUE_CLASS = "clazz";
	private static final String QUEUE_CAPACITY = "capacity";
	
	private static final String STAGE_NAME = "name";
	private static final String STAGE_EVENT_HANDLER = "eventHandler";
	private static final String STAGE_INPUT = "input";
	private static final String STAGE_OUTPUTS = "outputs";
	private static final String STAGE_ERROR_OUTPUT = "errorQueue";
	private static final String STAGE_MIN_THREAD = "minThreads";
	private static final String STAGE_MAX_THREAD = "maxThreads";
	
	private static final List<String> KEY_FILTER = new LinkedList<String>();
	
	static
	{
		KEY_FILTER.add(STAGE_NAME);
		KEY_FILTER.add(STAGE_EVENT_HANDLER);
		KEY_FILTER.add(STAGE_INPUT);
		KEY_FILTER.add(STAGE_OUTPUTS);
		KEY_FILTER.add(STAGE_ERROR_OUTPUT);
		KEY_FILTER.add(STAGE_MIN_THREAD);
		KEY_FILTER.add(STAGE_MAX_THREAD);
	}
	
	private static final String DEFAULT_QUEUE_CLASS_NAME = "com.iacrqq.seda.queue.FiniteQueue";
	
	private String name;
	
	private List<QueueDefinition> queueDefinitionList = new LinkedList<QueueDefinition>();
	private List<StageDefinition> stageDefinitionList = new LinkedList<StageDefinition>();
	private DispatcherDefinition dispatcherDefinition;
	
	private Configuration configuration = new MemoryConfiguration();
	
	private Map<String, Queue> queueMap = new HashMap<String, Queue>();
	private Map<String, Stage> stageMap = new HashMap<String, Stage>();
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	public void addQueueDefinition(QueueDefinition queue)
	{
		queueDefinitionList.add(queue);
	}
	
	public void addStageDefinition(StageDefinition stage)
	{
		stageDefinitionList.add(stage);
	}
	
	public void setDispatcherDefinition(DispatcherDefinition dispatcherDefinition)
	{
		this.dispatcherDefinition = dispatcherDefinition;
	}

	
	public Application toApplication()
	{
		Application app = new DefaultApplication();
		ApplicationAdjuster adjuster = new ApplicationAdjuster();
		app.setName(name);
		
		for(QueueDefinition queueDefinition : queueDefinitionList)
		{
			Queue queue = _construct_queue_(queueDefinition);
			if(queueMap.containsKey(queue.getName()))
			{
				throw new RuntimeException("Wrong application config file.");
			}
			
			app.addQueue(queue);
			adjuster.addQueueAdjuster(new QueueAdjuster(queue));
			if(queueMap.put(queue.getName(), queue) != null)
			{
				throw new RuntimeException("Wrong application config file.");
			}
		}
		
		for(StageDefinition stageDefinition : stageDefinitionList)
		{
			Stage stage = _construct_stage_(stageDefinition);
			app.addStage(stage);
			
			adjuster.addStageAdjuster(new StageAdjuster(stage, ApplicationAdjuster.DEFAULT_ADJUST_HISTORY, ApplicationAdjuster.DEFAULT_ADJUST_INTERVAL));
			if(stageMap.put(stage.getName(), stage) != null)
			{
				throw new RuntimeException("Wrong application config file.");
			}
		}
		
		app.setEventDispatcher(_construct_event_dispatcher_());
		
		app.setConfiguration(configuration);
		app.setAdjuster(adjuster);
		return app;
	}
	
	private Queue _construct_queue_(QueueDefinition queueDefinition)
	{
		String strClass = queueDefinition.getSingleValue(QUEUE_CLASS);
		if(strClass == null)
		{
			strClass = DEFAULT_QUEUE_CLASS_NAME;
		}
		try
		{
			Class clazz = Class.forName(strClass);
			Object obj = clazz.newInstance();
			
			((Queue)obj).setCapacity(Integer.valueOf(queueDefinition.getSingleValue(QUEUE_CAPACITY)));
			((Queue)obj).setName(queueDefinition.getName());
			
			return (Queue)obj;
		}
		catch(ClassNotFoundException e)
		{
			throw new RuntimeException(e);
		}
		catch(IllegalAccessException e)
		{
			throw new RuntimeException(e);
		}
		catch(InstantiationException e)
		{
			throw new RuntimeException(e);
		}
	}
	
	private Stage _construct_stage_(StageDefinition stageDefinition)
	{
		String strEventHanlderClass = stageDefinition.getSingleValue(STAGE_EVENT_HANDLER);
		try
		{
			Class clazz = Class.forName(strEventHanlderClass);
			Object obj = clazz.newInstance();
			Stage stage = new DefaultStage(stageDefinition.getName(), (EventHandler)obj, (Input<Event>)queueMap.get(stageDefinition.getSingleValue(STAGE_INPUT)), _get_outputs_(stageDefinition.getSingleOrMultiValue(STAGE_OUTPUTS)), (Output<Event>)queueMap.get(STAGE_ERROR_OUTPUT));
			configuration.set(new StringBuilder(stageDefinition.getName()).append(".executor.threadpool.minThreads").toString(), stageDefinition.getSingleValue(STAGE_MIN_THREAD));
			configuration.set(new StringBuilder(stageDefinition.getName()).append(".executor.threadpool.maxThreads").toString(), stageDefinition.getSingleValue(STAGE_MAX_THREAD));
			
			for(String key : stageDefinition.getPropertyKeysWithFilter(KEY_FILTER))
			{
				configuration.set(new StringBuilder(stageDefinition.getName()).append(".").append(key).toString(), stageDefinition.getSingleValue(key));
			}
			
			return stage;
		}
		catch(ClassNotFoundException e)
		{
			throw new RuntimeException(e);
		}
		catch(IllegalAccessException e)
		{
			throw new RuntimeException(e);
		}
		catch(InstantiationException e)
		{
			throw new RuntimeException(e);
		}
		
	}
	
	private List<Output<Event>> _get_outputs_(List<String> outputs)
	{
		List<Output<Event>> queues = new LinkedList<Output<Event>>();
		if(outputs == null || outputs.isEmpty())
		{
			return queues;
		}
		
		for(String str : outputs)
		{
			queues.add(queueMap.get(str));
		}
		
		return queues;
	}
	
	private EventDispatcher _construct_event_dispatcher_()
	{
		EventDispatcher eventDispatcher = new DefaultEventDispatcher();
		eventDispatcher.setName(dispatcherDefinition.getName());
		
		for(RouterEntryDefinition routerEntry : dispatcherDefinition.getRouterEntryDefinitionList())
		{
			Stage stage = stageMap.get(routerEntry.getStageName());
			if(stage == null)
			{
				throw new RuntimeException("Wrong application config file.");
			}
			eventDispatcher.registerRouterEntry(routerEntry.getState(), stage);
		}
		
		return eventDispatcher;
	}
}
